package cn.bang.mod

import cn.bang.mod.model.ScriptInfo
import cn.bang.mod.model.TranslData
import com.alibaba.excel.EasyExcel
import com.alibaba.excel.context.AnalysisContext
import com.alibaba.excel.read.listener.ReadListener
import java.io.File


class Moder(
    private val config: Configuration,
) {

    /**
     * 汉化脚本执行入口
     */
    fun localization() {

        // 查出所有游戏脚本 转成ScriptInfo [Hello.cs] => (filename, scriptContent)
        val scriptInfos = config.originScriptDir.listFiles { file -> file.extension == "cs" }!!
            .map { ScriptInfo(name = it.name, content = it.readText()) }

        // 先汉化能够知道名称的脚本，这些脚本名称没有被加密过.
        // know_name目录下的每一个Excel文件的数据都对应着相同名称的CS脚本
        // 这样不容以产生bug
        val knownNameScriptInfoList = config.knowNameExcelDir.listFiles()!!
            // 找到对应excel名称的脚本
            .map { excel ->
                excel to scriptInfos.find { excel.nameWithoutExtension + ".cs" == it.name }!!
            }
            // 汉化
            .onEach { (excel, scriptInfo) ->
                localizationNewScript(scriptInfo, excel)
            }
            // 返回scriptInfo，给后面过滤用，这样写有点😂....
            .map { (_, scriptInfo) -> scriptInfo }

        // 汉化对话
        val conversationScriptInfo = scriptInfos.find { Configuration.CONVERSATION_SCRIPT_SEARCH_KEY in it.content }!!
        localizationSpecial(
            scriptInfo = conversationScriptInfo,
            excelFile = config.conversationExcelFile,
            additionalTranslPairList = OtherTranslations.PRON_TRANSLATIONS + OtherTranslations.CONVERSATION_TRANSLATIONS
        )

        // 汉化周报
        val weeklyReportScriptInfo = scriptInfos.find { Configuration.WEEKLY_REPORT_SCRIPT_SEARCH_KEY in it.content }!!
        localizationSpecial(
            scriptInfo = weeklyReportScriptInfo,
            excelFile = config.weeklyReportExcelFile,
            additionalTranslPairList = OtherTranslations.PRON_TRANSLATIONS + OtherTranslations.WEEKLY_REPORT_TRANSLATIONS
        )

        // 比赛设置相关
        val matchScriptInfo = scriptInfos.find { Configuration.MATCH_SCRIPT_SEARCH_KEY in it.content }!!
        localizationNewScript(matchScriptInfo, config.matchExcelFile)

        // 汉化不知名的脚本 因为每一个版本的CS脚本是被重新加密过的，文件名不知被加密成什么
        // 所以只能提前从该脚本文件中提取唯一的英文单词或句子，去所有脚本文件中匹配

        // 下边先过滤掉已经汉化的脚本
        val filteredScriptInfoList =
            (scriptInfos - knownNameScriptInfoList.toSet() - conversationScriptInfo - weeklyReportScriptInfo - matchScriptInfo)
        // 声明finalScriptMap 用来存储被修改（汉化）过的叫文本
        val finalScriptMap = mutableMapOf<String, String>()
        readTransDataFromExcel(config.unknownScriptTranslDataExcelFile).forEach { (origin, local) ->
            filteredScriptInfoList.forEach { (filename, content) ->
                val currContent = finalScriptMap[filename] ?: content
                if (origin in currContent) {
                    finalScriptMap[filename] = currContent.carefulReplace(origin, local)
                }
            }
        }
        // 批量导出汉化后的不懂名的CS脚本
        finalScriptMap.forEach { (filename, content) ->
            File(config.outputDir.absolutePath sep filename).writeText(content)
        }
    }


    /**
     * 汉化 带变量的文本: AKA = "Hello " + man.name
     * 主要针对游戏中出现的剧情对话和周报
     */
    private fun localizationSpecial(
        scriptInfo: ScriptInfo, excelFile: File, additionalTranslPairList: List<Pair<String, String>> = listOf()
    ) {
        println("开始汉化：${scriptInfo.name}")

        // 读取翻译数据TranslData，然后转成一个Map, 结构：key = "Hello " + {0}; value = ("Hello " + name; , "你好 " + name;)
        val translDataMap =
            readTransDataFromExcel(excelFile, checking = true)
                .asSequence()
                .map { it.origin.toTemplate() to it }
                .distinctBy { (template, _) -> template }.toMap()

        // 将脚本按行转成字符串集合
        scriptInfo.content.lineSequence()
            // 查找疑似需要汉化的的文本
            .filter(::isTextNeedLocalization)
            .map { it.trim() }
            // 一般得到文本都是这样的 XXX = "Hello" + name;
            .distinct()
            .filter { it !in config.ignoreTexts}
            .map { text ->
                // prefix: XXX = "Hello" + name;  => XXX
                val prefix = text.substring(0, text.indexOf("=") + 1)
                // textContent: XXX = "Hello" + name;  => "Hello" + name;
                val textContent = text.replace(prefix, "").trim()
                // "Hello" + name;  => "Hello " + {0}; 然后在translDataMap中寻找
                translDataMap[textContent.toTemplate()]?.run {
                    // 如果文本有用+号拼接变量不是存字符串的化，那就填充变量
                    val localText = if (textContent.hasValuable) {
                        local.fillVariables(textContent.valuables)
                    } else {
                        local
                    }
                    TranslData(origin = text, local = prefix + localText)
                }.also { it ?: println("没有从Excel中找到该文本的中文文本\n${text}\n") }
            }
            .filter { it != null }
            .map { it!! }
            .fold(scriptInfo.content) { acc, (origin, local) ->
                acc.carefulReplace(origin, local)
            }
            // 汉化额外的翻译对列表， 因为这些内容不好使用上面的方法汉化，所以就额外处理了...
            .let { translatedScriptStr ->
                additionalTranslPairList.fold(translatedScriptStr) { acc, (origin, local) ->
                    acc.carefulReplace(origin, local)
                }
            }
            // 写出CS脚本
            .let { translatedScriptStr ->
                File(config.outputDir.absolutePath sep scriptInfo.name).writeText(translatedScriptStr)
                println("汉化${scriptInfo.name}完毕！\n")
            }
    }


    fun localizationNewScript(scriptInfo: ScriptInfo, excelFile: File) {
        println("正在将${scriptInfo.name}的翻译文本替换到${scriptInfo.name}")
        val newScriptContent = readTransDataFromExcel(excelFile).fold(scriptInfo.content) { acc, (origin, local) ->
            acc.carefulReplace(origin, local)
        }
        File(config.outputDir.absolutePath sep scriptInfo.name).writeText(newScriptContent)
        println("${scriptInfo.name}已经完成汉化\n")
    }

}


/**
 * 从Excel中读取TransData
 * @param checking 是否对excel文件进行检查，因为有时后会比较粗心把local改错
 */
fun readTransDataFromExcel(excel: File, checking: Boolean = false): List<TranslData> {
    val translDataSet = mutableSetOf<TranslData>()

    require(excel.exists())

    EasyExcel.read(excel, TranslData::class.java, object : ReadListener<TranslData> {
        override fun invoke(data: TranslData, context: AnalysisContext?) {
            if (data.origin.isNotEmpty() || data.local.isNotEmpty()) {
                translDataSet.add(data)
            }
        }

        override fun doAfterAllAnalysed(context: AnalysisContext?) {}
    }).sheet(excel.nameWithoutExtension).doRead()

    // 检查读取的语句是否正常
    if (checking) {
        println("开始检查${excel}的语句是否有问题！\n")
        val errorTotal = translDataSet.fold(0) { errorCount, (origin, local) ->
            val isOK = try {
                checkTranslDataIssues(origin, local)
            } catch (ex: Exception) {
                printlnErr(
                    """
                    语句检查出现异常
                    origin: $origin 
                    local: $local
                    
                """.trimIndent()
                ).let { false }
            }
            if (isOK) errorCount else errorCount + 1
        }
        check(errorTotal == 0) { throw RuntimeException("当前文件：${excel.name}的Local语法有错请更正！错误量：$errorTotal") }
    }
    return translDataSet.toList()
}

/**
 * 检查翻译语句是否符合语法规范（简陋的实现....） 只针对 对话文本 或者说 那些带边变量的文本...
 */
fun checkTranslDataIssues(origin: String, local: String): Boolean = when {
    // 检查是否分号结尾: >> "hello " + name
    local.last() != ';' -> printlnErr("该文本必须得使用分号结尾: \n ${local}\n").let { false }

    // 双引号是否不完整 >> "hello + name;
    local.count { it == '"' } % 2 != 0 -> printlnErr("双引号不完整: \n${local}\n").let { false }

    // 变量是否包含不正常的双引号 >> "hello " + name";
    local.valuables.map { it.toList() }.flatten()
        .filter { it == '"' }.size % 2 != 0 -> printlnErr("变量包含不正常的双引号: \n${local}\n").let { false }

    // 是否有多余的加号 >> "hello "+ name + ;
    local.strBlocks.count { it == "" } > 0 -> printlnErr("变量存在多余的加号: \n${local}\n").let { false }

    // 检查origin与local的变量是否匹配
    origin.valuables.size != local.valuables.size -> printlnErr("origin与local的变量不匹配: \n${origin}\n${local}\n").let { false }

    // 检测Local是否包含中文字符
    local.valuables.joinToString()
        .count { it in CHINESE_SYMBOLS } > 0 -> printlnErr("Local变量存在中文符号: \n${local}\n").let { false }

    // 那就没有问题
    else -> true
}